package com.beck.code.generator.generatorProcessor;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.engine.velocity.VelocityEngine;
import com.beck.code.generator.configProperty.CommonGeneratorConfig;
import com.beck.code.generator.configProperty.ContextConfig;
import com.beck.code.generator.configProperty.TableConfig;
import com.beck.code.generator.enmus.EntityFieldEnum;
import com.beck.code.generator.vo.AssociationEntityVO;
import com.beck.code.generator.vo.CollectionEntityVO;
import com.beck.code.generator.vo.CommonGeneratorVO;
import com.beck.code.generator.vo.EntityFieldPropertyVO;
import jakarta.persistence.*;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author CuiJianbo
 * @since 2025/7/1
 */
public abstract class CommonGenerator {

    protected ContextConfig contextConfig;

    protected CommonGeneratorConfig commonGeneratorConfig;

    protected TableConfig tableConfig;

    protected IntrospectedTable introspectedTable;

    protected TemplateEngine engine;

    protected String entityName;

    protected String entityFullName;

    private Class<?> entityClass;

    protected CommonGeneratorVO commonGeneratorVO;

    List<EntityFieldPropertyVO> fields;

    List<AssociationEntityVO> associations;

    List<CollectionEntityVO> collections;

    abstract void generateCodeFile();

    public CommonGenerator() {
    }

    public CommonGenerator(TableConfig tableConfig,
                           IntrospectedTable introspectedTable,
                           ContextConfig contextConfig,
                           CommonGeneratorConfig commonGeneratorConfig) {
        this.tableConfig = tableConfig;
        this.introspectedTable = introspectedTable;
        this.contextConfig = contextConfig;
        this.commonGeneratorConfig = commonGeneratorConfig;

        //初始化数据
        initData();

        //准备entity和关联entity字段数据
        prepareAllFieldProperties();

    }

    protected void initData() {
        String prefix = contextConfig.getTablePrefix();
        if (StrUtil.isNotEmpty(prefix)) {
            this.entityName = StrUtil.upperFirst(StrUtil.toCamelCase(introspectedTable.getTableName().substring(prefix.length())));
        } else {
            this.entityName = StrUtil.upperFirst(StrUtil.toCamelCase(introspectedTable.getTableName()));
        }

        entityFullName = StrUtil.concat(true, contextConfig.getEntityGeneratorConfig().getTargetPackage(), ".", entityName);

        try {
            entityClass = ClassLoader.getSystemClassLoader().loadClass(entityFullName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        engine = new VelocityEngine(new TemplateConfig(contextConfig.getTemplatePath(), TemplateConfig.ResourceMode.CLASSPATH));

        commonGeneratorVO = new CommonGeneratorVO();
        commonGeneratorVO.setTableName(tableConfig.getTableName());
        commonGeneratorVO.setEntityName(entityName);
        commonGeneratorVO.setEntityFullName(entityFullName);
        commonGeneratorVO.setTableAliasName(StrUtil.toUnderlineCase(entityName));
        commonGeneratorVO.setExtraProperties(contextConfig.getExtraPropertiesConfig().getExtraProperties());
        commonGeneratorVO.setPrimaryKeyStrategy(contextConfig.getPrimaryKeyStrategy());
        commonGeneratorVO.setSign(contextConfig.getAuthor());
        commonGeneratorVO.setSignDate(DateUtil.formatDate(DateUtil.date()));
    }


    protected List<Field> getCollectionJoinField() {
        return Arrays.stream(entityClass.getDeclaredFields())
                .filter(q -> q.getAnnotation(ManyToMany.class) != null ||
                        q.getAnnotation(OneToMany.class) != null)
                .collect(Collectors.toList());
    }

    protected List<Field> getAssociationJoinField() {
        return Arrays.stream(entityClass.getDeclaredFields())
                .filter(q -> q.getAnnotation(ManyToOne.class) != null ||
                        q.getAnnotation(OneToOne.class) != null)
                .collect(Collectors.toList());
    }


    protected void prepareAllFieldProperties() {
        List<EntityFieldPropertyVO> fields = new LinkedList<>();
        List<CollectionEntityVO> collections = new LinkedList<>();
        List<AssociationEntityVO> associations = new LinkedList<>();
        for (IntrospectedColumn introspectedColumn : introspectedTable.getIntrospectedColumns()) {
            EntityFieldPropertyVO entityFieldPropertyVO = new EntityFieldPropertyVO();
            if (introspectedColumn.getColumnName().equals("id")) {
                entityFieldPropertyVO.setFieldType(EntityFieldEnum.ID.getValue());
            } else {
                entityFieldPropertyVO.setFieldType(EntityFieldEnum.RESULT.getValue());
            }
            String tableName = introspectedTable.getTableName();
            if (StrUtil.isNotEmpty(contextConfig.getTablePrefix())) {
                tableName = tableName.substring(contextConfig.getTablePrefix().length());
            }
            String fullFieldName = StrUtil.concat(true, tableName, "_", introspectedColumn.getColumnName());
            String sqlFieldName = StrUtil.concat(true, tableName, ".", introspectedColumn.getColumnName());
            String property = StrUtil.toCamelCase(introspectedColumn.getColumnName());
            if (!property.equals("id") && property.endsWith("Id")) {
                continue;
            }
            entityFieldPropertyVO.setFullColumnName(fullFieldName);
            entityFieldPropertyVO.setJdbcType(introspectedColumn.getJdbcType().toUpperCase());
            entityFieldPropertyVO.setSqlColumn(sqlFieldName);
            entityFieldPropertyVO.setSqlTableColumn(introspectedColumn.getColumnName());
            entityFieldPropertyVO.setProperty(property);
            fields.add(entityFieldPropertyVO);
        }

        //解析组装Collection关联Entity
        List<Field> collectionFields = getCollectionJoinField();
        for (Field collectionField : collectionFields) {
            String fullName = ((ParameterizedType) collectionField.getGenericType()).getActualTypeArguments()[0].getTypeName();
            String voPackageName = contextConfig.getVoGeneratorConfig().getTargetPackage();
            String voFullName = StrUtil.concat(true, voPackageName, ".", collectionField.getType().getSimpleName(), "VO");
            List<EntityFieldPropertyVO> collectionEntityFields = new LinkedList<>();
            CollectionEntityVO collectionEntityVO = new CollectionEntityVO();
            collectionEntityVO.setJavaEntityFullName(fullName);
            collectionEntityVO.setJavaVoFullName(voFullName);
            collectionEntityVO.setPropertyName(collectionField.getName());
            collectionEntityVO.setUnderlinePropertyName(StrUtil.toUnderlineCase(collectionField.getName()));
            collectionEntityVO.setFields(collectionEntityFields);
            collections.add(collectionEntityVO);
            Class<?> collectionEntityClass;
            try {
                collectionEntityClass = ClassLoader.getSystemClassLoader().loadClass(fullName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

            String collectionEntityName = collectionEntityClass.getDeclaredAnnotation(Table.class).name();
            IntrospectedTable table = ContextGenerator.tables.stream().filter(p -> p.getTableName().equals(collectionEntityName)).findFirst().orElse(null);
            if (ObjectUtil.isNull(table)) {
                table = ContextGenerator.getTableMetaData(collectionEntityName);
            }
            for (IntrospectedColumn introspectedColumn : table.getIntrospectedColumns()) {
                if (introspectedColumn.getColumnName().endsWith("_id")) {
                    continue;
                }
                EntityFieldPropertyVO entityFieldPropertyVO = new EntityFieldPropertyVO();
                if (introspectedColumn.getColumnName().equals("id")) {
                    entityFieldPropertyVO.setFieldType(EntityFieldEnum.ID.getValue());
                } else {
                    entityFieldPropertyVO.setFieldType(EntityFieldEnum.RESULT.getValue());
                }
                String tableName = StrUtil.toUnderlineCase(collectionField.getName());
                String fullFieldName = StrUtil.concat(true, tableName, "_", introspectedColumn.getColumnName());
                entityFieldPropertyVO.setFullColumnName(fullFieldName);
                entityFieldPropertyVO.setProperty(StrUtil.toCamelCase(introspectedColumn.getColumnName()));
                collectionEntityFields.add(entityFieldPropertyVO);
            }

        }


        //解析组装Association关联Entity
        List<Field> associationJoinFields = getAssociationJoinField();
        for (Field associationField : associationJoinFields) {
            String fullName = associationField.getType().getTypeName();
            String voPackageName = contextConfig.getVoGeneratorConfig().getTargetPackage();
            String voFullName = StrUtil.concat(true, voPackageName, ".", associationField.getType().getSimpleName(), "VO");
            List<EntityFieldPropertyVO> associationEntityFields = new LinkedList<>();
            AssociationEntityVO associationEntityVO = new AssociationEntityVO();
            associationEntityVO.setJavaEntityFullName(fullName);
            associationEntityVO.setJavaVoFullName(voFullName);
            associationEntityVO.setPropertyName(associationField.getName());
            associationEntityVO.setUnderlinePropertyName(StrUtil.toUnderlineCase(associationField.getName()));
            associationEntityVO.setFields(associationEntityFields);
            associations.add(associationEntityVO);
            Class<?> collectionEntityClass;
            try {
                collectionEntityClass = ClassLoader.getSystemClassLoader().loadClass(fullName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }

            String associationEntityName = collectionEntityClass.getDeclaredAnnotation(Table.class).name();
            IntrospectedTable table = ContextGenerator.tables.stream().filter(p -> p.getTableName().equals(associationEntityName)).findFirst().orElse(null);
            if (ObjectUtil.isNull(table)) {
                table = ContextGenerator.getTableMetaData(associationEntityName);
            }
            for (IntrospectedColumn introspectedColumn : table.getIntrospectedColumns()) {
                if (introspectedColumn.getColumnName().endsWith("_id")) {
                    continue;
                }
                EntityFieldPropertyVO entityFieldPropertyVO = new EntityFieldPropertyVO();
                if (introspectedColumn.getColumnName().equals("id")) {
                    entityFieldPropertyVO.setFieldType(EntityFieldEnum.ID.getValue());
                } else {
                    entityFieldPropertyVO.setFieldType(EntityFieldEnum.RESULT.getValue());
                }
                String tableName = StrUtil.toUnderlineCase(associationField.getName());
                String fullFieldName = StrUtil.concat(true, tableName, "_", introspectedColumn.getColumnName());
                entityFieldPropertyVO.setFullColumnName(fullFieldName);
                entityFieldPropertyVO.setProperty(StrUtil.toCamelCase(introspectedColumn.getColumnName()));
                associationEntityFields.add(entityFieldPropertyVO);
            }

        }

        this.associations = associations;
        this.collections = collections;
        this.fields = fields;
    }

    protected String applyTemplateResources(String fileName, CommonGeneratorVO commonGeneratorVO) {
        Template template = engine.getTemplate(contextConfig.getTemplatePath() + "/" + fileName);
        return template.render(Dict.create().set("vo", commonGeneratorVO));
    }


    protected boolean generateCodeFile(String content) {
        String projectPath = System.getProperty("user.dir");
        String moduleName = contextConfig.getTargetModule();
        String fileName = fileName();
        if (fileName.endsWith(".java")) {
            fileName = "/src/main/java/" + fileName;
        } else if (fileName.endsWith(".xml")) {
            fileName = "/src/main/resources/" + fileName;
        }
        String filePath = StrUtil.concat(true, projectPath, "/", moduleName, fileName);
        if ((FileUtil.exist(filePath) && commonGeneratorConfig.getOverwrite()) || !FileUtil.exist(filePath)) {
            FileWriter writer = new FileWriter(filePath);
            writer.write(content);
            return true;
        }
        return false;
    }



    abstract String fileName();


}



